package com.shopx.core.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.http.*;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.UnsupportedEncodingException;
import java.net.UnknownHostException;
import java.util.*;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Slf4j
public class HttpConnectionPoolUtil {

  private static final int CONNECT_TIMEOUT = 30000;// 设置连接建立的超时时间为10s
  private static final int SOCKET_TIMEOUT = 30000;
  private static final int MAX_CONN = 800; // 最大连接数
  private static final int Max_PRE_ROUTE = 799;// 路由最大连接数
  private static final int MAX_ROUTE = 800;
  private static CloseableHttpClient httpClient; // 发送请求的客户端单例
  private static PoolingHttpClientConnectionManager manager; //连接池管理类
  private static ScheduledExecutorService monitorExecutor;

  private final static Object syncLock = new Object(); // 相当于线程锁,用于线程安全

  /**
   * 对http请求进行基本设置
   *
   * @param httpRequestBase http请求
   */
  private static void setRequestConfig(HttpRequestBase httpRequestBase) {
    RequestConfig requestConfig = RequestConfig.custom().setConnectionRequestTimeout(CONNECT_TIMEOUT)
        .setConnectTimeout(CONNECT_TIMEOUT)
        .setSocketTimeout(SOCKET_TIMEOUT).build();

    httpRequestBase.setConfig(requestConfig);
  }

  public static CloseableHttpClient getHttpClient() {
    if (httpClient == null) {
      //多线程下多个线程同时调用getHttpClient容易导致重复创建httpClient对象的问题,所以加上了同步锁
      synchronized (syncLock) {
        if (httpClient == null) {
          httpClient = createHttpClient();
          //开启监控线程,对异常和空闲线程进行关闭
          monitorExecutor = Executors.newScheduledThreadPool(1);
          monitorExecutor.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
              //关闭异常连接
              manager.closeExpiredConnections();
              //关闭5s空闲的连接
              manager.closeIdleConnections(5000, TimeUnit.MILLISECONDS);

            }
          }, 100, 100, TimeUnit.MILLISECONDS);
        }
      }
    }
    return httpClient;
  }

  /**
   */
  public static CloseableHttpClient createHttpClient() {
    ConnectionSocketFactory plainSocketFactory = PlainConnectionSocketFactory.getSocketFactory();
    LayeredConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactory.getSocketFactory();
    Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create().register("http", plainSocketFactory)
        .register("https", sslSocketFactory).build();

    manager = new PoolingHttpClientConnectionManager(registry);
    //设置连接参数
    manager.setMaxTotal(MAX_CONN); // 最大连接数
    manager.setDefaultMaxPerRoute(Max_PRE_ROUTE); // 路由最大连接数
    //请求失败时,进行请求重试
    HttpRequestRetryHandler handler = new HttpRequestRetryHandler() {
      @Override
      public boolean retryRequest(IOException e, int i, HttpContext httpContext) {
        if (i > 3) {
          //重试超过3次,放弃请求
          //log.error("retry has more than 3 time, give up request");
          return false;
        }
        if (e instanceof NoHttpResponseException) {
          //服务器没有响应,可能是服务器断开了连接,应该重试
          //log.error("receive no response from server, retry");
          return true;
        }
        if (e instanceof SSLHandshakeException) {
          // SSL握手异常
          //log.error("SSL hand shake exception");
          return false;
        }
        if (e instanceof InterruptedIOException) {
          //超时
          //log.error("InterruptedIOException");
          return false;
        }
        if (e instanceof UnknownHostException) {
          // 服务器不可达
          //log.error("server host unknown");
          return false;
        }
        if (e instanceof ConnectTimeoutException) {
          // 连接超时
          //log.error("Connection Time out");
          return false;
        }
        if (e instanceof SSLException) {
          //log.error("SSLException");
          return false;
        }

        HttpClientContext context = HttpClientContext.adapt(httpContext);
        HttpRequest request = context.getRequest();
        if (!(request instanceof HttpEntityEnclosingRequest)) {
          //如果请求不是关闭连接的请求
          return true;
        }
        return false;
      }
    };

    CloseableHttpClient client = HttpClients.custom().setConnectionManager(manager).setRetryHandler(handler).build();
    return client;
  }

  /**
   * 设置post请求的参数
   *
   * @param httpPost
   * @param params
   */
  private static void setPostParams(HttpPost httpPost, Map<String, String> params) {
    List<NameValuePair> nvps = new ArrayList<NameValuePair>();
    Set<String> keys = params.keySet();
    for (String key : keys) {
      nvps.add(new BasicNameValuePair(key, params.get(key)));
    }
    try {
      httpPost.setEntity(new UrlEncodedFormEntity(nvps, "utf-8"));
    } catch (UnsupportedEncodingException e) {
      log.warn("setPost Error:", e);
    }
  }

  public static String post(String url, Map<String, String> params) {
    String result = "";
    InputStream in = null;
    CloseableHttpResponse response = null;
    int needRetry = 3;
    while (needRetry > 0) {
      try {
        HttpPost httpPost = new HttpPost(url);
        setRequestConfig(httpPost);
        setPostParams(httpPost, params);
        httpPost.addHeader("Connection", "Keep-Alive");
        response = getHttpClient().execute(httpPost, HttpClientContext.create());
        HttpEntity entity = response.getEntity();
        if (entity != null) {
          in = entity.getContent();
          result = EntityUtils.toString(entity, "utf-8");
        }
        return result;
      } catch (NoHttpResponseException e) {
        log.warn("post " + needRetry + " Error:", e);
        needRetry -= 1;
      } catch (IOException e) {
        log.warn("post " + needRetry + " Error:", e);
        needRetry -= 2;
      } finally {
        try {
          if (in != null) in.close();
          if (response != null) response.close();
        } catch (IOException e) {
          log.warn("Close response Error:", e);
        }
      }
    }
    return result;
  }

  public static String post(String url, String content, String[] heads) {
    CloseableHttpResponse response = null;
    String result = null;
    int needRetry = 3;
    while (needRetry > 0) {
      try {
        HttpPost httpPost = new HttpPost(url);
        setRequestConfig(httpPost);
        for (int i = 0; i < heads.length; i += 2) {
          httpPost.addHeader(heads[i], heads[i + 1]);
        }
        httpPost.addHeader("Connection", "Keep-Alive");
        StringEntity entityParams = new StringEntity(content, "utf-8");
        httpPost.setEntity(entityParams);
        response = getHttpClient().execute(httpPost, HttpClientContext.create());
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode != HttpStatus.SC_OK) {
          log.warn("post to {} Code={}", url, statusCode);
        }
        HttpEntity entity = response.getEntity();
        if (entity != null) {
          result = EntityUtils.toString(entity, "utf-8");
          EntityUtils.consume(entity);
        }
        if (result != null && result.length() > 0)
          return result;
        needRetry--;
        if (needRetry > 0)
          log.info("post to {} retry {}", url, 3 - needRetry);
      } catch (NoHttpResponseException e) {
        log.warn("post " + needRetry + " Error:", e);
        needRetry -= 1;
      } catch (IOException e) {
        log.warn("post " + needRetry + " Error:", e);
        needRetry -= 2;
      } finally {
        try {
          if (response != null) response.close();
        } catch (IOException e) {
          log.warn("Close response Error:", e);
        }
      }
    }
    return result;
  }

  public static String httpPostByJSON(String url, String content) {
    CloseableHttpResponse response = null;
    String result = null;
    int needRetry = 3;
    while (needRetry > 0) {
      try {
        long beginTime = System.currentTimeMillis();// 开始时间
        HttpPost httpPost = new HttpPost(url);
        setRequestConfig(httpPost);
        httpPost.addHeader("Connection", "Keep-Alive");
        StringEntity entityParams = new StringEntity(content, "utf-8");
        entityParams.setContentType("application/json");
        httpPost.setEntity(entityParams);
        response = getHttpClient().execute(httpPost, HttpClientContext.create());
        int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode != HttpStatus.SC_OK) {
          log.warn("post to {} Code={}", url, statusCode);
        }
        HttpEntity entity = response.getEntity();
        if (entity != null) {
          result = EntityUtils.toString(entity, "utf-8");
          EntityUtils.consume(entity);
        }
        if (result != null && result.length() > 0) {
          long endTime = System.currentTimeMillis();// 2、结束时间
          long consumeTime = endTime - beginTime;// 3、消耗的时间
          log.info("请求时长:{}",consumeTime);
          return result;
        }
        needRetry--;
        if (needRetry > 0) {
          log.info("post to {} retry {}", url, 3 - needRetry);
        }
        long endTime = System.currentTimeMillis();// 2、结束时间
        long consumeTime = endTime - beginTime;// 3、消耗的时间
        log.info("请求时长:{}",consumeTime);
      } catch (Exception e) {
        log.error("http.Error:", e);
        needRetry -= 1;
      }  finally {
        try {
          if (response != null) response.close();
        } catch (IOException e) {
          log.warn("Close response Error:", e);
        }
      }
    }
    return result;
  }

  public static String get(String url) {
    CloseableHttpResponse response = null;
    int needRetry = 3;
    while (needRetry > 0) {
      try {
        HttpGet httpget = new HttpGet(url);
        setRequestConfig(httpget);
        httpget.addHeader("Connection", "Keep-Alive");
        response = getHttpClient().execute(httpget, HttpClientContext.create());
        HttpEntity entity = response.getEntity();
        String result = EntityUtils.toString(entity, "utf-8");
        EntityUtils.consume(entity);   //关闭HttpEntity是的流，如果手动关闭了InputStream instream = entity.getContent();这个流，也可以不调用这个方法
        return result;
      } catch (NoHttpResponseException e) {
        log.warn("get " + needRetry + " Error:", e);
        needRetry -= 1;
      } catch (IOException e) {
        log.warn("get " + needRetry + " Error:", e);
        needRetry -= 2;
      } finally {
        try {
          if (response != null)
            response.close();
        } catch (IOException e) {
          log.warn("Close response Error:", e);
        }
      }
    }
    return null;
  }

  public static byte[] getData(String url) {
    CloseableHttpResponse response = null;
    int needRetry = 3;
    while (needRetry > 0) {
      try {
        HttpGet httpget = new HttpGet(url);
        setRequestConfig(httpget);
        httpget.addHeader("Connection", "Keep-Alive");
        response = getHttpClient().execute(httpget, HttpClientContext.create());
        HttpEntity entity = response.getEntity();
        byte[] result = EntityUtils.toByteArray(entity);
        EntityUtils.consume(entity);   //关闭HttpEntity是的流，如果手动关闭了InputStream instream = entity.getContent();这个流，也可以不调用这个方法
        return result;
      } catch (NoHttpResponseException e) {
        log.warn("get " + needRetry + " Error:", e);
        needRetry -= 1;
      } catch (IOException e) {
        log.warn("get " + needRetry + " Error:", e);
        needRetry -= 2;
      } finally {
        try {
          if (response != null)
            response.close();
        } catch (IOException e) {
          log.warn("Close response Error:", e);
        }
      }
    }
    return null;
  }

  /**
   * 关闭连接池
   */
  public static void closeConnectionPool() {
    try {
      if (httpClient != null) httpClient.close();
      if (manager != null) manager.close();
      if (monitorExecutor != null) monitorExecutor.shutdown();
    } catch (IOException e) {
      log.warn(e.toString());
    } finally {
      httpClient = null;
      manager = null;
      monitorExecutor = null;
    }
  }
}
